Detailed Forward Chaining Example

The following is an example of a simple, five rule system for converting arabic positive integers to roman numerals. The code is divided into three separate files, ones for class definitions (the .h file), one for action (RHS) methods (the .c file), and one for the rule definitions, in the extended rule syntax (the .rrs file). Only the the rule syntax file needs to be run through the preprocessor.

In the following "ctx" is a global variable, for a context object.

The first file presented is the file "xlroman.h". In it are definitions for classes Roman, ConstructRoman, and ConstructRomanSub, all based on the class WMEObject (Working Memory Element Object).

////////////////////////////////////////////////////////////////////////
// Class definitions for roman numeral translation system
////////////////////////////////////////////////////////////////////////

class Roman: public WMEObject
{ protected:
  public:
    char numeral;
    int value;
    int below;

    Roman (char num, int val, int bel)
    { numeral=num;
      value=val;
      below=bel;
    }
}

class ConstructRoman: public WMEObject
{ protected:
  public:
    int number;

    ConstructRoman (int num)
    { number=num;
    }
}

class ConstructRomanSub: public WMEObject
{ protected:
  public:
    int goal;
    int above;
    int difference;

    ConstructRomanSub (int val, int abv, int dif)
    { goal=val;
      above=abv;
      difference=dif;
    }
}

The next file we present is "xlroman.c". In this file we define the methods doSubRule, doNormal, doSubtraction, doZero, and InstallNumeralKnowledge, which sets up the roman numeral database.

////////////////////////////////////////////////////////////////////////
// Action routines for Roman numeral translation.
////////////////////////////////////////////////////////////////////////

#include "xlroman.h"

void doSubRule (Context *ctx, ConstructRoman *goal, int gnum, int above)
{ ctx->RemoveWMEObject(goal);
  ctx->Tell(new ConstructRomanSub(ctx->State(), gnum, above, above-gnum));
}

void doNormal (context *ctx, ConstructRoman *goal, Roman *db, int gnum,
               int dbval)
{ ctx->emit(db->numeral);
  ctx->Tell(new ConstructRoman(ctx->State(), gnum-dbval));
  ctx->RemoveWMEObject(goal);
}

void doSubtraction (context *ctx, ConstructRoman *goal, Roman *db1,
                    Roman *db2, int gnum, int db1val int db2val)
{ ctx->emit(db2->numeral);
  ctx->emit(db1->numeral);
  ctx->RemoveWMEObject(goal);
  ctx->Tell(new ConstructRoman(ctx->State(), db2val+gnum-db1val));
}

void doZero (context *ctx, ConstructRoman *goal)
{ ctx->RemoveWMEObject(goal); }

void doNegative (context *ctx, ConstructRoman *goal)
{ ctx->emit('-');         // I did it
  ctx->Tell(new ConstructRoman(ctx->getMyState(),
                               -goal->number));
  ctx->RemoveWMEObject(goal);
}

// Code to setup the numeral database.
void InstallNumeralKnowledge (context *ctx)
{ ctx->Tell(new Roman('I', 1, 0));
  ctx->Tell(new Roman('V', 5, 4));
  ctx->Tell(new Roman('X', 10, 9));
  ctx->Tell(new Roman('L', 50, 40));
  ctx->Tell(new Roman('C', 100, 90));
  ctx->Tell(new Roman('D', 500, 400));
  ctx->Tell(new Roman('M', 1000, 900));
}

The last file we present is "xlroman.rrs", the rule file. We describe the condition part of the first rule, next.

In ROMAN1, below, there are four conditions in the condition part. We are matching against a ConstructRoman object, which we will call goal (locally), and binding gnum to its number slot. Thus gnum is the number we are currently seeking. We are also matching against a Roman object, which we will call above, binding aval and alow in the process, and which must meet the additional constraints that aval is greater than gnum and alow is less than or equal to gnum. In addition, the condition part of this rule will only match if there are NOT two Roman objects, filter1 and filter2, in working memory, such that the value of filter1 is less than aval and greater than gnum, and such that the value of filter2 is equal to gnum. In other words, there must not be a Roman whose value equals our target, nor a Roman which meets the constraints of the above object, and is a closer fit to our target. When this condition part matches working memory, the rule is a candidate for selection, and if selected, invokes the doSubRule method on the context object, the goal object, the goal number, and the value of the next greater Roman numeral, as arguments.

////////////////////////////////////////////////////////////////////////

// Translate a number to a roman numeral representation

// Rule to handle the subtraction case
// If a number doesn't have a direct match, but is within the range
// of another numeral and there is no smaller number that straddles
// goal, use the subtraction rule.
// eg:  45
// 45 >=40 and 45 <50 so subtract 45 from 50 for L and then translate 5.

ROMAN1 forward_rule {
  Condition: {ConstructRoman goal [gnum=goal->number]},
             {Roman above [aval=above->value, alow=above->below]
              (aval > gnum) and (alow <= gnum)},  // bracket val
            ~{Roman filter1 [f1val=filter1->value]
              (f1val < aval) and (f1val > gnum)},
            ~{Roman filter2 [f2val=filter2->value] (f2val==gnum)};
  Action: doSubRule(ctx, goal, gnum, aval);
  author: "Norman St. John Polevaulter";
  module: NumberToRoman;
}

// Handles the normal case. Issue the numeral less than the number,
// and continue with the remainder.
// eg: 7
// 5 (V) is less than 7, so emit V and continue to translate 7-5=2


ROMAN2 forward_rule {
  Condition: {ConstructRoman goal [gnum=goal->number]},
            ~{Roman filter1 [f1val=filter1->value, f1low=filter1->below]
              (f1val>gnum) and (f1low<=gnum)},
             {Roman db [dbval=db->value] (dbval<=gnum)},
            ~{Roman filter2 [f2val=filter2->value]
              (f2val>dbval) and (f2val<=gnum)};
  Action: doNormal(ctx, goal, gnum, dbval);
  author: "Norman St. John Polevaulter";
  module: NumberToRoman;
}

// Implements a subtraction by finding the appropriate numerals

ROMAN3 forward_rule {
  Condition: {ConstructRomanSub goal [gabv=goal->above, 
gdiff=goal->difference]},
             {Roman db1 [db1val=db1->value] (db1val==gabv)},
             {Roman db2 [db2val=db2->value] (db2val>=gdiff)},
            ~{Roman filter [fval=filter->value]
              (fval>=goal->difference) and (fvalnumber] (gnum<0)};
  Action: doNegative;
  author: "Norman St. John Polevaulter";
  module: NumberToRoman;
}

// Finally the trivial case where the number is zero -- nothing

ROMAN5 forward_rule {
  Condition: {ConstructRoman goal [gnum=goal->number] (gnum==0)};
  Action: doZero(ctx, goal);
  author: "Norman St. John Polevaulter";
  module: NumberToRoman;
}

// Fin

Back
Doll Home Page
<